#ifndef NOSAL_CORE_ERROR_CODE_H_
#define NOSAL_CORE_ERROR_CODE_H_

#include "nosal/core/error_domain.h"
#include "nosal/core/abort.h"

#include <ostream>
#include <utility>
#include <exception>
#include <type_traits>

namespace netaos {
namespace nosal {
namespace core {

class ErrorCode
{
    friend std::ostream& operator<<(std::ostream& out, ErrorCode const& e)
    {
        return (out << e.mDomain->Name() << ":" << e.mValue << ":" << e.mSupportData);
    }

    using CodeType = ErrorDomain::CodeType;
    using SupportDataType = ErrorDomain::SupportDataType;

public:
    template <typename EnumT, typename = typename std::enable_if<std::is_enum<EnumT>::value>::type>
    constexpr ErrorCode(EnumT e, SupportDataType data = 0) noexcept
        // Call MakeErrorCode() unqualified, so the correct overload is found via ADL.
        : ErrorCode(MakeErrorCode(e, data))
    { }

    constexpr ErrorCode(CodeType value, ErrorDomain const& domain, SupportDataType data = 0) noexcept
        : mValue(value)
        , mSupportData(data)
        , mDomain(&domain)
    { }

    constexpr CodeType Value() const noexcept
    {
        return mValue;
    }

    constexpr SupportDataType SupportData() const noexcept
    {
        return mSupportData;
    }

    constexpr ErrorDomain const& Domain() const noexcept
    {
        return *mDomain;
    }

    std::string Message() const noexcept
    {
        return Domain().Message(Value());
    }

    [[noreturn]] void ThrowAsException() const noexcept(false)
    {
        Domain().ThrowAsException(*this);

        // Never reached, but apparently needed to avoid warnings from certain compilers (such as 5.4.0).
        Abort("ErrorCode::ThrowAsException returned");
    }

private:
    CodeType mValue;
    SupportDataType mSupportData;
    ErrorDomain const* mDomain;  // non-owning pointer to the associated ErrorDomain
};

constexpr inline bool operator==(ErrorCode const& lhs, ErrorCode const& rhs)
{
    return lhs.Domain() == rhs.Domain() && lhs.Value() == rhs.Value();
}

constexpr inline bool operator!=(ErrorCode const& lhs, ErrorCode const& rhs)
{
    return lhs.Domain() != rhs.Domain() || lhs.Value() != rhs.Value();
}

namespace ifc
{

// Helper function for other Functional Clusters' error domains.
template <typename ExceptionType>
[[noreturn]] void ThrowOrTerminate(ErrorCode errorCode)
{
#ifndef NOSAL_NO_EXCEPTIONS
    throw ExceptionType(std::move(errorCode));
#else
    (void)errorCode;
    std::terminate();
#endif
}

}  // namespace ifc

}  // namespace core
}  // namespace nosal
}  // namespace netaos

#endif  // NOSAL_CORE_ERROR_CODE_H_
